package business;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import util.AdviceTimesToAct;
import util.Globals;
import util.IntertypeType;
import exception.NoNetLogoCodeFoundToGenerateException;
import exception.NoNetLogoOthersPartsOfApplicationFoundToGenerateException;
import exception.NoScopeReferingByVarException;

public class Weaver {
	private AspectFile aspectFiles;
	private NetLogoFile netLogoFiles;
	private AspectComponents aspectComponentNoAffectNetlogoCode;

	public Weaver(){}

	public Weaver(AspectFile aspectFiles, String fileReadLocalization, String fileToGenerateLocalization) throws IOException{
		this.aspectFiles = aspectFiles;
		this.netLogoFiles = new NetLogoFile(fileReadLocalization,fileToGenerateLocalization);
	}

	/**
	 * Iniciates the weaven process to create new file with the netlogo code 
	 * and the aspects component included.
	 * @throws Exception 
	 */
	public void iniciateWeavenProcess() throws Exception{
		/* Reads the netlogo imported file */
		netLogoFiles.readImportedFile();
		/* Reads and validate the aspect imported file */
		aspectFiles.validate();
		/* Create a fail components aspect list */
		aspectComponentNoAffectNetlogoCode = aspectFiles.checkAspectComponentsAtuationInNetLogoCode(netLogoFiles.getFileRead().getNetlogoCode());
		/* Join the aspect code with the original code (in netlogo) */
		joinCodes();
		/* Generate a file and save in the specified path */
		generateAndSaveJoinedFile();
	}

	/**
	 * Validate the aspect from screen.
	 * 
	 * @throws Exception
	 */
	public AspectFile validateAspectFromScreen() throws Exception{
		/* Reads and validate the aspect retrived from screen */
		aspectFiles.validate();
		return aspectFiles;
	}

	/**
	 * Generate join aspect from screen.
	 * 
	 * @throws Exception
	 */
	public void joinPropertiesFromScreen() throws Exception{
		//TODO to set netlogo code
		netLogoFiles.readImportedFile();
		/* Join the aspect code with the original code (in netlogo) */
		joinCodes();
		/* Generate a file and save in the specified path */
		generateAndSaveJoinedFile();
	}

	/**
	 * Joins all aspects component in the same file.
	 * @throws Exception 
	 */
	private void joinCodes() throws Exception{
		joinIntertypes();
		joinAdvicePointcuts();
	}

	/**
	 * Joins the intertype with the netlogo code.
	 * @throws NoScopeReferingByVarException 
	 */
	private void joinIntertypes() throws NoScopeReferingByVarException{
		for(Aspect aspect : aspectFiles.getAspectListToManipulate()){
			for(Intertype intertype : aspect.getAspectComponents().getIntertypeList()){
				if(intertype.isValidInclude()){
					/* If its not a var declaration, puts it on the end code. */
					if(!(intertype.getType() == IntertypeType.VAR || intertype.getType() == IntertypeType.GLOBAL)){
						netLogoFiles.addCodeInLastPosition(intertype.getCodeToInclude(), netLogoFiles.getFileToGenerate().getNetlogoCode());
					}else{ /* Else, puts it on the correct block. */
						boolean isFunctionScope = true;
						String varBlock = intertype.getPlaceToInclude();
						CodePosition codePosition = null;

						if(intertype.getType() == IntertypeType.VAR){
							/* Finds a place to add code. */
							codePosition = Globals.findIdFunctionBeginCodeLineAndEndCodeLine(varBlock, netLogoFiles.getFileRead().getNetlogoCode());
						}else{
							/* Finds a place to add code. */
							codePosition = Globals.findLineToAddVar(varBlock, netLogoFiles.getFileRead().getNetlogoCode());
							/* Finds declaration bracket begin. */
							codePosition = Globals.findNextBracket(codePosition, netLogoFiles.getFileRead().getNetlogoCode());
							isFunctionScope = false;
						}
						/* Inserts var declaration on function. */
						netLogoFiles.addVarDeclarationOnFunction(intertype.getCodeToInclude(), varBlock, netLogoFiles.getFileToGenerate().getNetlogoCode().getCodeLines(), codePosition, isFunctionScope);
					}
				}
				/* Ajust the generated code. */
				makeAjustsOnAlteratedCode(netLogoFiles.getFileToGenerate().getNetlogoCode());
			}
		}
	}


	/**
	 * Joins all advices and pointcuts with the netlogo code.
	 * @throws Exception 
	 */
	private void joinAdvicePointcuts() throws Exception{
		Map<String,List<AdvicePointcut>> advicesAndPointcuts = createMapOfAdvicesAndPointcuts();

		Set<String> idsFunctionSet = advicesAndPointcuts.keySet();

		/* Iterate for each map key. */
		for(String actualIdFunction : idsFunctionSet){
			List<AdvicePointcut> advicePointcutList = advicesAndPointcuts.get(actualIdFunction);

			/* Iterate for each advicePointcut list (retrived from map position). */
			for(AdvicePointcut advicePointcut : advicePointcutList){
				String idFunction = advicePointcut.getFunctionIdentifier();
				String targetCode = advicePointcut.getLinesToAct();
				String newCode = advicePointcut.getIncludeMethod();
				List<CodePosition> placesOnCodeAffected = new ArrayList<CodePosition>();

				/* Check the components of pointcut filled. */
				boolean haveTargetCode = (targetCode!=null && !targetCode.equals(""));
				boolean haveFunctionId = (idFunction!=null && !idFunction.equals(""));

				if(haveTargetCode && !haveFunctionId){
					placesOnCodeAffected = Globals.findTargetCodeReferenced(netLogoFiles.getFileRead().getNetlogoCode().getCodeLines(), targetCode);
				}else if(haveFunctionId && !haveTargetCode){
					//codePositionList = findFunctionsReferenced();
				}else{

				}

				//				
				//				if(!Globals.isJockerPointcut(idFunction)){
				//					/* If the id pointcut isnt a jocker, identifies the local on the code and puts on the functionsOnCodeAffected. */
				//					CodePosition codePosition = Globals.findIdFunctionBeginCodeLineAndEndCodeLine(idFunction, netLogoFiles.getFileToGenerate().getNetlogoCode());
				//					placesOnCodeAffected.add(codePosition);
				//				}else{
				//					if(idFunction.equalsIgnoreCase(Globals.POINTCUT_ONLY_JOCKER_IDENTIFIER)){
				//						/* If the id is '..' the code affect will be all functions on the code. */
				//						for(Line line : netLogoFiles.getFileToGenerate().getNetlogoCode().getNetlogoLinesFunctionNames()){
				//							/* Creates arraylist to put the id line. */
				//							ArrayList<Line> linesList = new ArrayList<Line>();
				//							linesList.add(line);
				//
				//							/* Gets position zero from arraylist (contents the id function). */
				//							String iterateActualIdFunction = Globals.extractIdFuntionsFromFunctionNamesLinesList(linesList).get(Globals.ZERO);
				//
				//							/* Sets functionsOnCodeAffected. */
				//							placesOnCodeAffected = Globals.extractFunctionPosition(placesOnCodeAffected, iterateActualIdFunction, netLogoFiles);
				//						}
				//					}else{
				//						/* Extracts id functions from lines id list.*/
				//						List<String> functionIdList = Globals.extractIdFuntionsFromFunctionNamesLinesList(netLogoFiles.getFileToGenerate().getNetlogoCode().getNetlogoLinesFunctionNames());
				//
				//						/* If list is not empty: */ 
				//						if(functionIdList != null && !functionIdList.isEmpty()){
				//							/* For each id on list, checks if its the function id received by parameter. */
				//							for(String actualFunctionId : functionIdList){
				//
				//								/* Checks if actual id is equals function id or function id is equals "..". */
				//								if(actualFunctionId.equalsIgnoreCase(idFunction) || idFunction.equals(Globals.POINTCUT_ONLY_JOCKER_IDENTIFIER)){
				//									/* Sets functionsOnCodeAffected. */
				//									placesOnCodeAffected = Globals.extractFunctionPosition(placesOnCodeAffected, actualFunctionId, netLogoFiles);
				//								}       
				//
				//								if(Globals.isJockerPointcut(idFunction)){
				//									/* If function id is a jocker, verify the localization (begin or end). */
				//									boolean isJockerInBegin = Globals.isJockerPointcutInTheBegin(idFunction);
				//
				//									/* Remove the jocker character "@". */
				//									String idWithoutJocker = idFunction.replace(Globals.POINTCUT_JOCKER_IDENTIFIER, Globals.SPACE).trim();
				//
				//									/* Checks if function id without jocker character is equals actual function id. */
				//									if(idWithoutJocker.equalsIgnoreCase(actualFunctionId)){
				//										/* Sets functionsOnCodeAffected. */
				//										placesOnCodeAffected = Globals.extractFunctionPosition(placesOnCodeAffected, actualFunctionId, netLogoFiles);
				//									}else if (actualFunctionId.length() >= idWithoutJocker.length() && idWithoutJocker.length() > 1){
				//										/* Checks if function id is on actual function id. */
				//										if((isJockerInBegin && (actualFunctionId.substring(actualFunctionId.length()-idWithoutJocker.length()).equalsIgnoreCase(idWithoutJocker)))
				//												|| (!isJockerInBegin && (actualFunctionId.substring(Globals.ZERO, idWithoutJocker.length()).equalsIgnoreCase(idWithoutJocker)))){
				//											/* Sets functionsOnCodeAffected. */
				//											placesOnCodeAffected = Globals.extractFunctionPosition(placesOnCodeAffected, actualFunctionId, netLogoFiles);
				//										}
				//									}
				//								}
				//							}
				//						}
				//					}
				//				}

				/* For each position of alteration on code, replaces the old code to the new one. */
				for(CodePosition codePosition : placesOnCodeAffected){
					String joinedCode = joinCode(codePosition, advicePointcut.getTimeToAct(), newCode, netLogoFiles.getFileToGenerate().getNetlogoCode());

					/* Replaces the old code, for the new (joined code). */
					netLogoFiles.replaceOldCode(codePosition, joinedCode);

					/* Ajusts the generated code. */
					makeAjustsOnAlteratedCode(netLogoFiles.getFileToGenerate().getNetlogoCode());
				}
			}
		}
	}

	/**
	 * Join a new code with the original netlogo code.
	 * This join can be after, around or before the referenced code.
	 * 
	 * @param CodePosition
	 * @param partToAdd
	 * @param timeToAct
	 * @return
	 */
	public static String joinCode(CodePosition codePosition, AdviceTimesToAct timeToAct, String codeToAdd, NetlogoCode netlogoCode){
		String newCode = "";

		List<Line> codeLinesList = netlogoCode.getCodeLines().subList
				(codePosition.getBeginLineCode().getPosition(), codePosition.getEndLineCode().getPosition()+1);

		/* Check the time to act, to get the place to insert. */
		if(timeToAct == AdviceTimesToAct.AFTER){
			for(Line line : codeLinesList){
				newCode = newCode + line.getValue();
			}
			newCode = newCode + " " + codeToAdd;
		}else if(timeToAct == AdviceTimesToAct.BEFORE){
			newCode = codeToAdd + " ";
			for(Line line : codeLinesList){
				newCode = newCode + line.getValue();
			}
		}else{
			newCode = codeToAdd;
		}
		return newCode;
	}

	/**
	 * Creates a map with all advices and pointcuts together.
	 * 
	 * @return
	 * @throws Exception 
	 */
	private Map<String,List<AdvicePointcut>> createMapOfAdvicesAndPointcuts() throws Exception{
		Map<String,List<AdvicePointcut>> advicesAndPointcuts = new HashMap<String,List<AdvicePointcut>>();

		/* For each valid advice (it + pointcut associated), create a AdvicePointcut object and put in the map. */
		for(Aspect aspect : this.aspectFiles.getAspectListToManipulate()){
			if(aspect.getAspectComponents().getAdviceList()!=null){
				for(Advice advice: aspect.getAspectComponents().getAdviceList()){
					for(Pointcut pointcut : aspect.getAspectComponents().getPointcutList()){
						/* If the pointcut is present on code, do: */
						if(pointcut.isInNetlogoCode()){
							if(true){
								boolean isEqualsPointcutId = false;

								/* If pointcut id is in the list pointcut refering, sets isEqualsPointcutId true. */
								if(advice.getPointcutsReferingList()!=null && !advice.getPointcutsReferingList().isEmpty()){
									for(String actualIdPointcut : advice.getPointcutsReferingList()){
										if(actualIdPointcut.equalsIgnoreCase(pointcut.getPointcutId())){
											isEqualsPointcutId = true;
											break;
										}
									}
								}

								/* If is the same id (isEqualsPointcutId is true), do: */
								if(isEqualsPointcutId){
									/* Creating a key to be used on map. Can reference a functionId or a target code. */
									String keyToFindOnMap = null;
									if(pointcut.getFunctionIdentifier()!=null && !pointcut.getFunctionIdentifier().equals("")){
										keyToFindOnMap = pointcut.getFunctionIdentifier();
									}else if(pointcut.getLinesToAct()!=null && !pointcut.getLinesToAct().equals("")){
										keyToFindOnMap = pointcut.getLinesToAct();
									}else{
										throw new Exception("There is no functionId neither target code related to pointcut: " + pointcut.getPointcutId());
									}

									/* Gets the list related to the keyToFindOnMap. */
									List<AdvicePointcut> advicePointcutList = advicesAndPointcuts.get(keyToFindOnMap);

									/* Checks if exists a id list to the function associated. */
									if(advicePointcutList == null){
										/* If not, creates a list. */
										advicePointcutList = new ArrayList<AdvicePointcut>();
									}
									/* Creates a AdvicePointcut Object and puts on the advicePointcut list. */
									advicePointcutList.add(new AdvicePointcut(advice.getTimeToAct(), advice.getIncludeMethod(), 
											pointcut.getType(), pointcut.getLinesToAct(), pointcut.getFunctionIdentifier(), pointcut.getFunctionFormals(), 
											pointcut.getLogicalOperator(), pointcut.getDesignator()));

									/* Puts the list on the map. The key is the id function. */
									advicesAndPointcuts.put(keyToFindOnMap, advicePointcutList);
								}
							}
						}
					}
				}
			}

			/* For each advicePointcut, puts it in the map. */ 
			if(aspect.getAspectComponents().getAdvicePointcutList()!=null){
				for(AdvicePointcut advicePointcut : aspect.getAspectComponents().getAdvicePointcutList()){
					/* Creating a key to be used on map. Can reference a functionId or a target code. */
					String keyToFindOnMap = null;
					if(advicePointcut.getFunctionIdentifier()!=null && advicePointcut.getFunctionIdentifier().equals("")){
						keyToFindOnMap = advicePointcut.getFunctionIdentifier();
					}else if(advicePointcut.getLinesToAct()!=null && advicePointcut.getLinesToAct().equals("")){
						keyToFindOnMap = advicePointcut.getLinesToAct();
					}else{
						throw new Exception("There is no functionId neither target code related to a advicePointcut.");
					}

					List<AdvicePointcut> advicePointcutList = advicesAndPointcuts.get(keyToFindOnMap);
					/* Checks if exists a id list to the function associated. */
					if(advicePointcutList == null){
						advicePointcutList = new ArrayList<AdvicePointcut>();
					}
					/* Puts the id on the list. */
					advicePointcutList.add(advicePointcut);
					/* Puts the list on the map. The key can be the functionId or target code. */
					advicesAndPointcuts.put(keyToFindOnMap, advicePointcutList);
				}
			}
		}
		return advicesAndPointcuts;
	}

	/**
	 * Generates a joined file and save in the correct path.
	 * 
	 * @throws IOException
	 * @throws NoNetLogoOthersPartsOfApplicationFoundToGenerateException
	 * @throws NoNetLogoCodeFoundToGenerateException
	 */
	private void generateAndSaveJoinedFile() throws IOException, NoNetLogoOthersPartsOfApplicationFoundToGenerateException, NoNetLogoCodeFoundToGenerateException{
		netLogoFiles.getFileManipulate().generateFile(netLogoFiles.getFileToGenerate());
	}

	/**
	 * Make ajusts on code after any alteration.
	 * 
	 * @param netlogoCode
	 */
	public void makeAjustsOnAlteratedCode(NetlogoCode netlogoCode){
		netLogoFiles.setCorrectPositionOnCode(netlogoCode);
		netLogoFiles.setCorrectFunctionListNames(netlogoCode);
	}

	public AspectFile getAspectFiles() {
		return aspectFiles;
	}

	public AspectComponents getAspectComponentNoAffectNetlogoCode() {
		return aspectComponentNoAffectNetlogoCode;
	}
}
